package com.gsbelarus.gedemin.salary.service;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.gsbelarus.gedemin.lib.sync.protocol.SyncNotificationAdapter;
import com.gsbelarus.gedemin.lib.sync.protocol.SyncProtocol;
import com.gsbelarus.gedemin.lib.sync.protocol.SyncServiceModel;
import com.gsbelarus.gedemin.lib.sync.protocol.entity.SyncProperty;
import com.gsbelarus.gedemin.lib.sync.protocol.entity.SyncServiceTask;
import com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException;
import com.gsbelarus.gedemin.salary.R;
import com.gsbelarus.gedemin.salary.activity.Main;
import com.gsbelarus.gedemin.salary.database.RealmHelper;
import com.gsbelarus.gedemin.salary.entity.SyncGdMsgStateModelSerializer;
import com.gsbelarus.gedemin.salary.entity.SyncRequestType;
import com.gsbelarus.gedemin.salary.entity.model.DayEventModel;
import com.gsbelarus.gedemin.salary.entity.model.EmployeeModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgEmployeeModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgHolidayModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgIncomeModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgInfoModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgVacationModel;
import com.gsbelarus.gedemin.salary.entity.model.GdMsgWorkScheduleModel;
import com.gsbelarus.gedemin.salary.entity.model.PayslipBenefitModel;
import com.gsbelarus.gedemin.salary.entity.model.PayslipDeductionModel;
import com.gsbelarus.gedemin.salary.entity.model.PayslipItemModel;
import com.gsbelarus.gedemin.salary.entity.model.PayslipModel;
import com.gsbelarus.gedemin.salary.entity.model.SyncGdMsgStateModel;
import com.gsbelarus.gedemin.salary.entity.model.SyncInfoModel;
import com.gsbelarus.gedemin.salary.entity.model.WorkScheduleModel;
import com.gsbelarus.gedemin.salary.fragment.PersonInfoPrefFragment;
import com.gsbelarus.gedemin.salary.fragment.SettingsFragment;
import com.gsbelarus.gedemin.salary.fragment.gdmsg.GdMsgsListFragment;
import com.gsbelarus.gedemin.salary.util.CurrencyHelper;
import com.gsbelarus.gedemin.salary.util.ExtractStringHelper;
import com.gsbelarus.gedemin.salary.util.GCMHelper;
import com.gsbelarus.gedemin.salary.util.HolidaysHelper;
import com.gsbelarus.gedemin.salary.util.ModelFactory;
import com.gsbelarus.gedemin.salary.util.WebServiceManager;
import com.orhanobut.logger.Logger;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.realm.Realm;
import io.realm.RealmList;

public class SyncService extends SyncServiceModel<SyncRequestType> {

    private enum DataClassName {

        EMPLOYEE(EmployeeModel.class),

        PAYSLIP(PayslipModel.class),
        PAYSLIP_BENEFIT(PayslipBenefitModel.class),
        PAYSLIP_DEDUCTION(PayslipDeductionModel.class),
        PAYSLIP_ITEM(PayslipItemModel.class),

        WORK_SCHEDULE(WorkScheduleModel.class),
        DAY_EVENT(DayEventModel.class),

        GD_MSG(GdMsgModel.class),
        GD_MSG_INCOME(GdMsgIncomeModel.class),
        GD_MSG_HOLIDAY(GdMsgHolidayModel.class),
        GD_MSG_EMPLOYEE(GdMsgEmployeeModel.class),
        GD_MSG_VACATION(GdMsgVacationModel.class),
        GD_MSG_WORK_SCHEDULE(GdMsgWorkScheduleModel.class),
        GD_MSG_INFO(GdMsgInfoModel.class),

        SYNC_GD_MSG_STATE(SyncGdMsgStateModel.class),

        UNKNOWN(null);

        private String className = "";

        DataClassName(Class cl) {
            if (cl != null)
                this.className = cl.getSimpleName();
        }

        public String getClassName() {
            return className;
        }

        public DataClassName setClassName(String className) {
            this.className = className;
            return this;
        }
    }

    public static final String RESPONSE_PARAM_EMPL_ID            = "empl_id";
    public static final String RESPONSE_PARAM_ERROR_MSG          = "error_msg";
    public static final String RESPONSE_PARAM_LAST_SYNC_DATE     = "last_sync_date";
    public static final String RESPONSE_PARAM_LAST_SYNC_MSG_DATE = "last_sync_msg_date";
    public static final String RESPONSE_PARAM_LAST_SYNC_KEY      = "last_sync_key";

    public static final String REQUEST_TOKEN_SYNC_GD_MSG         = "gdmnSalarySyncMsg";
    public static final String REQUEST_TOKEN_SYNC                = "gdmnSalarySync";
    public static final String REQUEST_TOKEN_SIGN_IN             = "gdmnSalarySignIn";

    public static final String SERVER_DATE_FORMAT = "yyyy-MM-dd";
    public static final String SERVER_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm:ss";

    private RealmHelper realmHelperSyncThread;
    private Realm realmUIThread;
    private Gson gsonModelParser;
    private Gson gsonModelWithDateTimeParser;
    private CurrencyHelper currencyHelper;

    private Map<Integer,String> inboxMsgNotificationTexts;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        currencyHelper = CurrencyHelper.getInstance(context);
        realmUIThread = Realm.getInstance(context);

        inboxMsgNotificationTexts = new HashMap<>();

        GsonBuilder builder = new GsonBuilder();
        builder.setDateFormat(SERVER_DATE_FORMAT);
        gsonModelParser = builder.excludeFieldsWithoutExposeAnnotation().create();

        builder = new GsonBuilder();
        builder.setDateFormat(SERVER_DATE_TIME_FORMAT);
        gsonModelWithDateTimeParser = builder.excludeFieldsWithoutExposeAnnotation().create();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        realmUIThread.close();
    }

    /**
     * Вызывается если пришел кастомный Task
     *
     * @param syncServiceTask содержит кастомный Task
     * @return если true - выполнение синхронизации продолжится с полученым Task-ом
     * если false - выполнение синхронизации с полученным Task-ом прекратится
     */
    @Override
    protected boolean onStartSubTask(SyncServiceTask<SyncRequestType> syncServiceTask) {
        RealmHelper realmHelper = new RealmHelper(Realm.getInstance(context));
        if (isWorkMode()) {
            switch (syncServiceTask.getSubTask().getTypeOfRequest()) {
                case DATA_MSG_REQUEST:
                    SyncInfoModel syncInfoModel = realmHelper.getSyncInfo(true);
                    boolean authIsNeeded = syncInfoModel.getEmplId() == null || syncInfoModel.getEmplId().isEmpty();
                    if (authIsNeeded)
                        startTask(new SyncServiceTask<SyncRequestType>(SyncServiceTask.TypeOfTask.SERIES_SYNC)
                                .setSubTask(new SyncRequestType(SyncRequestType.TypeOfRequest.AUTH_REQUEST)));

                    startTask(new SyncServiceTask<SyncRequestType>(SyncServiceTask.TypeOfTask.SERIES_SYNC)
                            .setSubTask(new SyncRequestType(SyncRequestType.TypeOfRequest.DATA_REQUEST)));

                    startTask(new SyncServiceTask<SyncRequestType>(SyncServiceTask.TypeOfTask.SERIES_SYNC)
                            .setSubTask(new SyncRequestType(SyncRequestType.TypeOfRequest.MSG_REQUEST)));

                    realmHelper.getRealm().close();
                    return false;
            }
        }
        realmHelper.getRealm().close();
        return super.onStartSubTask(syncServiceTask);
    }

    /**
     * Выполняется во время подготовки к созданию нового потока загрузки, имеет доступ к UI потоку
     */
    @Override
    public void onPreExecute() {
        super.onPreExecute();
        currencyHelper.onStartUpdateDB();
    }

    /**
     * Открыть транзакцию, выполняется в потоке загрузки
     */
    @Override
    public void onBeginTransaction() {
        Logger.d("onBeginTransaction");
        realmHelperSyncThread = new RealmHelper(Realm.getInstance(context));
        realmHelperSyncThread.getRealm().beginTransaction();

        if (getCurrentTask().getSubTask() != null) {
            switch (getCurrentTask().getSubTask().getTypeOfRequest()) {
                case AUTH_REQUEST:
                    realmHelperSyncThread.clearDB(false);
                    break;
            }
        }
    }

    /**
     * Вызывается после получения ответа, в потоке загрузки.
     * Для вывода ошибки необходимо сгененировать исключение CustomSyncException("локализованное название ошибки")
     *
     * @param headerMap массив праметров из заголовка.
     * @throws com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException прерывает загрузку данных, отображает диолог об ошибке
     */
    @Override
    public void onHeaderLoaded(HashMap<String, String> headerMap) throws CustomSyncException {
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            Logger.d(key + "=" + value);
        }
        if (headerMap.containsKey(RESPONSE_PARAM_ERROR_MSG)) {
            CustomSyncException customSyncException = new CustomSyncException(headerMap.get(RESPONSE_PARAM_ERROR_MSG));

            Logger.e(customSyncException, customSyncException.getMessage());
            //Crashlytics.getInstance().core.logException(customSyncException);
            throw customSyncException;
        }

        if (headerMap.containsKey(RESPONSE_PARAM_EMPL_ID))
            realmHelperSyncThread.getSyncInfo(false).setEmplId(headerMap.get(RESPONSE_PARAM_EMPL_ID));

        if (headerMap.containsKey(RESPONSE_PARAM_LAST_SYNC_KEY))
            realmHelperSyncThread.getSyncInfo(false).setLastSyncKey(headerMap.get(RESPONSE_PARAM_LAST_SYNC_KEY));

        if (headerMap.containsKey(RESPONSE_PARAM_LAST_SYNC_DATE))
            realmHelperSyncThread.getSyncInfo(false).setLastSyncDate(headerMap.get(RESPONSE_PARAM_LAST_SYNC_DATE));

        if (headerMap.containsKey(RESPONSE_PARAM_LAST_SYNC_MSG_DATE))
            realmHelperSyncThread.getSyncInfo(false).setLastSyncMsgDate(headerMap.get(RESPONSE_PARAM_LAST_SYNC_MSG_DATE));

        if (getCurrentTask().getSubTask().getTypeOfRequest() == SyncRequestType.TypeOfRequest.SEND_GD_MSG_STATES) {
            List<SyncGdMsgStateModel> syncGdMsgStateModels = realmHelperSyncThread.getAll(SyncGdMsgStateModel.class);
            realmHelperSyncThread.deleteObjectList(syncGdMsgStateModels, false);
        }
    }

    /**
     * Вызывается после получения ответа, в потоке загрузки.
     * Для вывода ошибки необходимо сгененировать исключение CustomSyncException("локализованное название ошибки")
     *
     * @param lines тело блока в виде строк. Следует распарсить и вставить в БД.
     * @throws com.gsbelarus.gedemin.lib.sync.protocol.exception.CustomSyncException прерывает загрузку данных, отображает диолог об ошибке
     */
    @Override
    public void onBlockLoaded(List<String> lines) throws CustomSyncException {
        Logger.d("lines count =" + lines.size());
        GdMsgModel responseGdMsg;
        try {
            for (String line : lines) {
                Logger.d("line =" + line);

                DataClassName dataClassName = getDataClass(line);
                if (dataClassName == DataClassName.UNKNOWN) {
                    CustomSyncException customSyncException = new CustomSyncException("Unknown class name:\n" + dataClassName.getClassName());

                    Logger.e(customSyncException, customSyncException.getMessage());
                    //Crashlytics.getInstance().core.logException(customSyncException);
                    throw customSyncException;
                }
                line = line.replace(dataClassName.getClassName(), "");

                switch (dataClassName) {
                    case EMPLOYEE:
                        EmployeeModel employeeModel = gsonModelParser.fromJson(line, EmployeeModel.class);
                        employeeModel = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(employeeModel);
                        PayslipModel employeePS = realmHelperSyncThread.getPayslipByUID(employeeModel.getUid());
                        if (employeePS != null && employeePS.getEmployee() == null)
                            employeePS.setEmployee(employeeModel);

                        break;
                    case PAYSLIP:
                        PayslipModel payslip = gsonModelParser.fromJson(line, PayslipModel.class);
                        String employeeKey = payslip.getEmployeeKey();
                        payslip = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(payslip);
                        if (payslip.getEmployee() == null && employeeKey != null)
                            payslip.setEmployee(realmHelperSyncThread.getEmployeeByUID(employeeKey));

                        RealmList<PayslipItemModel> items = new RealmList<>();
                        items.addAll(realmHelperSyncThread.getItemsByPayslipKey(payslip.getUid()));
                        payslip.setItems(items);

                        RealmList<PayslipBenefitModel> benefits = new RealmList<>();
                        benefits.addAll(realmHelperSyncThread.getBenefitsByPayslipKey(payslip.getUid()));
                        payslip.setBenefits(benefits);

                        RealmList<PayslipDeductionModel> deductions = new RealmList<>();
                        deductions.addAll(realmHelperSyncThread.getDeductionsByPayslipKey(payslip.getUid()));
                        payslip.setDeductions(deductions);

                        break;
                    case PAYSLIP_ITEM:
                        PayslipItemModel item = gsonModelParser.fromJson(line, PayslipItemModel.class);
                        String payslipKeyItem = item.getPayslipKey();
                        item = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(item);
                        PayslipModel itemPS = realmHelperSyncThread.getPayslipByUID(payslipKeyItem);
                        if (itemPS != null)
                            itemPS.getItems().add(item);

                        break;
                    case PAYSLIP_DEDUCTION:
                        PayslipDeductionModel deduction = gsonModelParser.fromJson(line, PayslipDeductionModel.class);
                        String payslipKeyDeduction = deduction.getPayslipKey();
                        deduction = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(deduction);
                        PayslipModel deductionPS = realmHelperSyncThread.getPayslipByUID(payslipKeyDeduction);
                        if (deductionPS != null)
                            deductionPS.getDeductions().add(deduction);

                        break;
                    case PAYSLIP_BENEFIT:
                        PayslipBenefitModel benefit = gsonModelParser.fromJson(line, PayslipBenefitModel.class);
                        String payslipKeyBenefit = benefit.getPayslipKey();
                        benefit = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(benefit);
                        PayslipModel benefitPS = realmHelperSyncThread.getPayslipByUID(payslipKeyBenefit);
                        if (benefitPS != null)
                            benefitPS.getBenefits().add(benefit);

                        break;
                    case WORK_SCHEDULE:
                        WorkScheduleModel workScheduleModel = gsonModelParser.fromJson(line, WorkScheduleModel.class);
                        WorkScheduleModel wSExist = realmHelperSyncThread.getWorkScheduleByDay(workScheduleModel.getDate());
                        if (wSExist != null) {
                            wSExist.setUid(workScheduleModel.getUid());
                            workScheduleModel.getEvents().addAll(0, wSExist.getEvents());
                        }
                        String employeeKeyWS = workScheduleModel.getEmployeeKey();
                        workScheduleModel = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(workScheduleModel);
                        if (workScheduleModel.getEmployee() == null)
                            workScheduleModel.setEmployee(realmHelperSyncThread.getEmployeeByUID(employeeKeyWS));

                        break;
                    case DAY_EVENT:
                        DayEventModel event = gsonModelParser.fromJson(line, DayEventModel.class);
                        double hours = event.getHours();
                        String workScheduleKey = event.getWorkScheduleKey();
                        event = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(event);
                        WorkScheduleModel eventWS = realmHelperSyncThread.getWorkScheduleByUID(workScheduleKey);
                        if (eventWS != null) {
                            Calendar calendar = Calendar.getInstance();
                            calendar.setTime(eventWS.getDate());
                            calendar.set(Calendar.HOUR_OF_DAY, 1);
                            event.setTimeBegin(new Date(calendar.getTimeInMillis()));
                            calendar.add(Calendar.MINUTE, (int) (hours * 60));
                            event.setTimeEnd(new Date(calendar.getTimeInMillis()));

                            if (eventWS.getDayType() == WorkScheduleModel.DayType.DAY_OFF)
                                event.setAllDay(true);
                            eventWS.getEvents().add(event);
                        }

                        break;
                    case GD_MSG:
                        GdMsgModel gdMsg = gsonModelWithDateTimeParser.fromJson(line, GdMsgModel.class);
                        if (!realmHelperSyncThread.existOnUid(gdMsg, gdMsg.getUid())) {
                            realmHelperSyncThread.getRealm().copyToRealm(gdMsg);

                            if (gdMsg.isUnread() && gdMsg.getSender() == GdMsgModel.Sender.SERVER) {
                                inboxMsgNotificationTexts.put(gdMsg.getUid(), ExtractStringHelper.getGdMsgRequestSubject(gdMsg) + ". ");
                            }
                        }
                        break;
                    case GD_MSG_HOLIDAY:            //только для серверных сообщений
                        GdMsgHolidayModel holiday = gsonModelWithDateTimeParser.fromJson(line, GdMsgHolidayModel.class);
                        holiday.setRequestGdMsgModel(realmHelperSyncThread.getGdMsgByUID(holiday.getRequestMsgKey()));
                        if (!realmHelperSyncThread.existOnUid(holiday, holiday.getUid())) {
                            holiday = realmHelperSyncThread.getRealm().copyToRealm(holiday);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(holiday.getUid());
                            responseGdMsg.setGdMsgHolidayModel(holiday);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }
                        break;
                    case GD_MSG_INCOME:             //только для серверных сообщений
                        GdMsgIncomeModel income = gsonModelWithDateTimeParser.fromJson(line, GdMsgIncomeModel.class);
                        GdMsgModel gdMsgIncome = realmHelperSyncThread.getGdMsgByUID(income.getRequestMsgKey());
                        income.setRequestGdMsgModel(gdMsgIncome);
                        if (!realmHelperSyncThread.existOnUid(income, income.getUid())) {
                            income = realmHelperSyncThread.getRealm().copyToRealm(income);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(income.getUid());
                            responseGdMsg.setGdMsgIncomeModel(income);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }
                        break;
                    case GD_MSG_VACATION:           //только для серверных сообщений
                        GdMsgVacationModel vacation = gsonModelWithDateTimeParser.fromJson(line, GdMsgVacationModel.class);
                        GdMsgModel gdMsgVacation = realmHelperSyncThread.getGdMsgByUID(vacation.getRequestMsgKey());
                        vacation.setRequestGdMsgModel(gdMsgVacation);
                        if (!realmHelperSyncThread.existOnUid(vacation, vacation.getUid())) {
                            vacation = realmHelperSyncThread.getRealm().copyToRealm(vacation);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(vacation.getUid());
                            responseGdMsg.setGdMsgVacationModel(vacation);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }
                        break;
                    case GD_MSG_EMPLOYEE:           //только для серверных сообщений
                        GdMsgEmployeeModel employee = gsonModelWithDateTimeParser.fromJson(line, GdMsgEmployeeModel.class);
                        GdMsgModel gdMsgEmployee = realmHelperSyncThread.getGdMsgByUID(employee.getRequestMsgKey());
                        employee.setRequestGdMsgModel(gdMsgEmployee);
                        if (!realmHelperSyncThread.existOnUid(employee, employee.getUid())) {
                            employee = realmHelperSyncThread.getRealm().copyToRealm(employee);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(employee.getUid());
                            responseGdMsg.setGdMsgEmployeeModel(employee);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }
                        break;
                    case GD_MSG_WORK_SCHEDULE:       //только для серверных сообщений
                        GdMsgWorkScheduleModel workSchedule = gsonModelWithDateTimeParser.fromJson(line, GdMsgWorkScheduleModel.class);
                        GdMsgModel gdMsgWorkSchedule = realmHelperSyncThread.getGdMsgByUID(workSchedule.getRequestMsgKey());
                        workSchedule.setRequestGdMsgModel(gdMsgWorkSchedule);
                        if (!realmHelperSyncThread.existOnUid(workSchedule, workSchedule.getUid())) {
                            workSchedule = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(workSchedule);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(workSchedule.getUid());
                            responseGdMsg.setGdMsgWorkScheduleModel(workSchedule);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }
                        break;
                    case GD_MSG_INFO:               //только для серверных сообщений
                        GdMsgInfoModel info = gsonModelWithDateTimeParser.fromJson(line, GdMsgInfoModel.class);
                        GdMsgModel gdMsgInfo = realmHelperSyncThread.getGdMsgByUID(info.getRequestMsgKey());
                        info.setRequestGdMsgModel(gdMsgInfo);
                        if (!realmHelperSyncThread.existOnUid(info, info.getUid())) {
                            info = realmHelperSyncThread.getRealm().copyToRealmOrUpdate(info);
                            responseGdMsg = realmHelperSyncThread.getGdMsgByUID(info.getUid());
                            responseGdMsg.setGdMsgInfoModel(info);
                            responseGdMsg.setGdMsgString(ExtractStringHelper.gdMsgToString(responseGdMsg));
                        }

                        break;
                    case SYNC_GD_MSG_STATE:
                        final SyncGdMsgStateModel stateModel = gsonModelParser.fromJson(line, SyncGdMsgStateModel.class);
                        realmHelperSyncThread.applyGdMsgState(stateModel, false);

                        //TODO делать прочитанным на сервере
                        // удаляем нотификацию о сообщении, для которого пришел статус
                        if (inboxMsgNotificationTexts.containsKey(stateModel.getGdMsgKey()))
                            inboxMsgNotificationTexts.remove(stateModel.getGdMsgKey());

                        break;
                    case UNKNOWN:
                        break;
                }
            }
        } catch (RuntimeException e) {
            Logger.e(e, e.getMessage());
            //Crashlytics.getInstance().core.logException(e);
            throw new CustomSyncException(e.getMessage());
        }
    }

    /**
     * Закрыть транзакцию успешно, выполняется в потоке загрузки
     */
    @Override
    public void onEndTransaction() {
        Logger.d("onEndTransaction");

        if (getCurrentTask().getSubTask() != null) {
            switch (getCurrentTask().getSubTask().getTypeOfRequest()) {
                case DATA_REQUEST:
                    currencyHelper.updatesRatesToBackend(realmHelperSyncThread, false);
                    HolidaysHelper holidaysHelper = new HolidaysHelper(context);
                    holidaysHelper.bindPublicHolidaysToBackend(realmHelperSyncThread);
                    holidaysHelper.bindMissedWeekendsToBackend(realmHelperSyncThread);
                    break;
            }
        }

        realmHelperSyncThread.getRealm().commitTransaction();
        realmHelperSyncThread.getRealm().close();
        realmHelperSyncThread = null;
    }

    /**
     * Закрыть транзакцию с откатом изменений, выполняется в потоке загрузки
     */
    @Override
    public void onCancelTransaction() {
        Logger.d("onCancelTransaction");
        realmHelperSyncThread.getRealm().cancelTransaction();
        realmHelperSyncThread.getRealm().close();
        realmHelperSyncThread = null;
    }

    /**
     * Выполняется после загрузки, имеет доступ к UI потоку
     *
     * @param status статус с каким завершилась синхронизация
     *               (NO_INTERNET_CONNECTION, ADDRESS_FAILED, TIMEOUT, NOT_REQUIRED,
     *               ELSE_FAILED, SUCCESSFUL, STOP_SYNC, CUSTOM_SYNC_FAILED)
     */
    @Override
    public void onPostExecute(SyncProtocol.SyncStatus status) {
        Logger.d("status=" + status.toString() + " " + status.getMessage());

        currencyHelper.onFinishUpdateDB();
        if (status == SyncProtocol.SyncStatus.SUCCESSFUL) {
            new GCMHelper(context).checkRegistration();
            if (!inboxMsgNotificationTexts.isEmpty()) {
                new SyncNotification(context).showNewMsgNotification(inboxMsgNotificationTexts, Main.class, GdMsgsListFragment.class);
            }
        }

        // после загрузки сообщений и их состояний отправляем свои состояния
        SyncRequestType requestType = getCurrentTask().getSubTask();
        RealmHelper realmHelper = new RealmHelper(Realm.getInstance(context));
        List<SyncGdMsgStateModel> syncGdMsgStateModels = realmHelper.getAll(SyncGdMsgStateModel.class);
        if (requestType != null && requestType.getTypeOfRequest() == SyncRequestType.TypeOfRequest.MSG_REQUEST &&
                !syncGdMsgStateModels.isEmpty()) {

            startTask(new SyncServiceTask<SyncRequestType>(SyncServiceTask.TypeOfTask.SERIES_SYNC)
                    .setSubTask(new SyncRequestType(SyncRequestType.TypeOfRequest.SEND_GD_MSG_STATES)));
        } else {

            realmHelper.getRealm().close();
            super.onPostExecute(status);
        }
    }

    /**
     * @return параметры для запросов, вызывается перед каждой синхронизацией
     */
    @Override
    protected SyncProperty getSyncProperty() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        RealmHelper realmHelper = new RealmHelper(Realm.getInstance(context));
        SyncInfoModel syncInfoModel = realmHelper.getSyncInfo(true);

        SyncProperty.SyncOneBlockBuilder syncOneBlockBuilder = new SyncProperty.SyncOneBlockBuilder();
        if (getCurrentTask().getSubTask() != null) {
            Map<String, String> requestParams = new HashMap<>();
            switch (getCurrentTask().getSubTask().getTypeOfRequest()) {
                case MSG_REQUEST:
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_TOKEN, REQUEST_TOKEN_SYNC_GD_MSG);
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_EMPL_ID, syncInfoModel.getEmplId());
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_LAST_SYNC_KEY, syncInfoModel.getLastSyncKey());
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_LAST_SYNC_MSG_DATE, syncInfoModel.getLastSyncMsgDate());

                    syncOneBlockBuilder
                            .setUrl(WebServiceManager.buildRequestUrl("http", prefs.getString(SettingsFragment.ADDRESS_SERVER, ""), requestParams))
                            .setDelayBetweenReconnect(60 * 1000)
                            .setCountConnections(3);
                    break;
                case DATA_REQUEST:
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_TOKEN, REQUEST_TOKEN_SYNC);
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_EMPL_ID, syncInfoModel.getEmplId());
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_LAST_SYNC_KEY, syncInfoModel.getLastSyncKey());
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_LAST_SYNC_DATE, syncInfoModel.getLastSyncMsgDate());

                    syncOneBlockBuilder
                            .setUrl(WebServiceManager.buildRequestUrl("http", prefs.getString(SettingsFragment.ADDRESS_SERVER, ""), requestParams))
                            .setDelayBetweenReconnect(40 * 1000)
                            .setCountConnections(3);
                    break;
                case AUTH_REQUEST:
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_TOKEN, REQUEST_TOKEN_SIGN_IN);
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_PERSONAL_NUMBER, prefs.getString(PersonInfoPrefFragment.PERSON_NUMBER, ""));
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_LIST_NUMBER, prefs.getString(PersonInfoPrefFragment.LIST_NUMBER, ""));
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_FIRSNAME, prefs.getString(PersonInfoPrefFragment.FIRSTNAME, ""));
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_MIDDLENAME, prefs.getString(PersonInfoPrefFragment.MIDDLENAME, ""));
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_SURNAME, prefs.getString(PersonInfoPrefFragment.SURNAME, ""));

                    syncOneBlockBuilder
                            .setUrl(WebServiceManager.buildRequestUrl("http", prefs.getString(SettingsFragment.ADDRESS_SERVER, ""), requestParams))
                            .setDelayBetweenReconnect(20 * 1000)
                            .setCountConnections(1);
                    break;
                case SEND_GD_MSG_STATES:
                    List<SyncGdMsgStateModel> syncGdMsgStateModels = realmHelper.getAll(SyncGdMsgStateModel.class);

                    GsonBuilder builder = new GsonBuilder();
                    builder.setDateFormat(SyncService.SERVER_DATE_TIME_FORMAT);
                    try {
                        builder.registerTypeAdapter(Class.forName("io.realm.SyncGdMsgStateModelRealmProxy"), new SyncGdMsgStateModelSerializer());
                    } catch (ClassNotFoundException ignore) {}
                    Gson gson = builder.excludeFieldsWithoutExposeAnnotation().create();

                    //String jsonData = gson.toJson(syncGdMsgStateModels);                          TODO
                    String jsonData = "[";

                    for (final SyncGdMsgStateModel state : syncGdMsgStateModels) {
                        if (!jsonData.equals("["))
                            jsonData += ",";
                        jsonData += gson.toJson(state);
                    }
                    jsonData += "]";

                    Logger.json(jsonData);

                    requestParams.put(SendMsgDataService.REQUEST_PARAM_TOKEN, SendMsgDataService.REQUEST_TOKEN_SEND_GD_MSG_STATES);
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_EMPL_ID, realmHelper.getSyncInfo(true).getEmplId());
                    if (realmHelper.getSyncInfo(true).getGcmRegistrationId() != null && !realmHelper.getSyncInfo(true).getGcmRegistrationId().isEmpty())
                        requestParams.put(SendMsgDataService.REQUEST_PARAM_GCM_REG_ID, realmHelper.getSyncInfo(true).getGcmRegistrationId());
                    requestParams.put(SendMsgDataService.REQUEST_PARAM_GD_MSG_STATE_ARRAY, jsonData);

                    syncOneBlockBuilder
                            .setUrl(WebServiceManager.buildRequestUrl("http", prefs.getString(SettingsFragment.ADDRESS_SERVER, ""), requestParams))
                            .setDelayBetweenReconnect(20 * 1000)
                            .setCountConnections(3);

                    break;
            }
        }
        realmHelper.getRealm().close();

        return syncOneBlockBuilder.build();
    }

    @Override
    protected void onCreateDemoDB() {
        RealmHelper realmHelper = new RealmHelper(Realm.getInstance(context));
        if (realmHelper.getSyncInfo(true).getEmplId().isEmpty()  && realmHelper.getLastEmployee() == null) {

            realmHelper.clearDB(true);
            realmHelper.initializeDemoData();

            GdMsgModel gdMsg = ModelFactory.newInstanceGdMsgInfoModel(
                    realmHelper.generateUID(GdMsgInfoModel.class),
                    new GdMsgInfoModel(),
                    realmHelper.generateUID(GdMsgModel.class),
                    new GdMsgModel(),
                    getString(R.string.welcome_text),
                    getString(R.string.created_demo_db_text),
                    new Date(),
                    GdMsgModel.RequestStatus.UNKNOWN);

            Map<Integer,String> notifyTexts = new HashMap<>();
            notifyTexts.put(gdMsg.getUid(), ExtractStringHelper.getGdMsgRequestSubject(gdMsg) + ". ");
            new SyncNotification(context).showNewMsgNotification(notifyTexts, Main.class, GdMsgsListFragment.class);

            realmHelper.save(gdMsg, true);
        }
        realmHelper.getRealm().close();
        CurrencyHelper.getInstance(context).updatesRates(null);
    }

    /**
     * @return в каком режиме проводить синхронизацию. Вызывается перед каждой синхронизацей.
     */
    @Override
    protected boolean isWorkMode() {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.WORK_MODE, false);
    }

    @Override
    protected SyncNotificationAdapter getSyncNotificationAdapter() {
        return new SyncNotification(context);
    }

    private DataClassName getDataClass(String line) {
        for (DataClassName item : DataClassName.values()) {
            if (item == DataClassName.UNKNOWN) return item.setClassName(line);
            if (line.contains(item.getClassName()))
                return item;
        }
        return DataClassName.UNKNOWN;
    }
}
